import pandas as pd
import numpy as np
from prophet import Prophet
from sklearn.preprocessing import MinMaxScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
import lightgbm as lgb
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.font_manager import FontProperties

# 设置中文字体
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=12) 

# --- 1. 数据加载 --- 
def load_data(file_path):
    try:
        # 读取CSV文件，使用正确的列名
        df = pd.read_csv(file_path)
        # 将列名重命名为Prophet所需的格式
        df = df.rename(columns={'date': 'ds', 'exchange_rate': 'y'})
         # 确保日期列为datetime类型
        df['ds'] = pd.to_datetime(df['ds'])
        return df
    except Exception as e:
        print(f"加载数据时发生错误: {str(e)}")
        return None

# --- 2. Prophet 模型 --- 
def train_predict_prophet(df, periods=3):
    """使用 Prophet 模型进行训练和预测"""
    model = Prophet()
    model.fit(df)
    future = model.make_future_dataframe(periods=periods)
    forecast = model.predict(future)
    return model, forecast

# --- 3. LSTM 模型 --- 
def create_dataset(dataset, look_back=1):
    dataX, dataY = [], []
    for i in range(len(dataset) - look_back - 1):
        a = dataset[i:(i + look_back), 0]
        dataX.append(a)
        dataY.append(dataset[i + look_back, 0])
    return np.array(dataX), np.array(dataY)

def train_predict_lstm(df, look_back=7, epochs=50, batch_size=1, periods=3):
    """使用 LSTM 模型进行训练和预测"""
    data = df['y'].values.reshape(-1, 1)
    scaler = MinMaxScaler(feature_range=(0, 1))
    scaled_data = scaler.fit_transform(data)

    # 划分训练集和测试集 (此处主要用于回测评估，实际预测用全部历史数据)
    # train_size = int(len(scaled_data) * 0.8)
    # train, test = scaled_data[0:train_size,:], scaled_data[train_size-look_back:,:] # 保证test有足够的look_back
    
    # 为了预测未来，我们用所有数据训练
    train_X, train_Y = create_dataset(scaled_data, look_back)
    if train_X.shape[0] == 0:
        print("错误：创建的数据集为空，可能是数据量过少或look_back设置过大。")
        return None, None, None

    train_X = np.reshape(train_X, (train_X.shape[0], 1, train_X.shape[1]))

    model = Sequential()
    model.add(LSTM(50, input_shape=(1, look_back)))
    model.add(Dense(1))
    model.compile(loss='mean_squared_error', optimizer='adam')
    model.fit(train_X, train_Y, epochs=epochs, batch_size=batch_size, verbose=0)

    # 进行预测
    last_sequence = scaled_data[-look_back:]
    predictions_scaled = []
    current_input = last_sequence.reshape((1, 1, look_back))

    for _ in range(periods):
        pred_scaled = model.predict(current_input, verbose=0)
        predictions_scaled.append(pred_scaled[0,0])
        # 更新输入序列，移除最旧的值，添加新的预测值
        new_input_val = pred_scaled.reshape(1,1,1) # Reshape to (1, 1, 1) for concatenation
        current_input = np.concatenate((current_input[:, :, 1:], new_input_val), axis=2)
        
    predictions = scaler.inverse_transform(np.array(predictions_scaled).reshape(-1,1))
    
    # 创建预测的日期
    last_date = df['ds'].iloc[-1]
    future_dates = pd.to_datetime([last_date + pd.Timedelta(days=i) for i in range(1, periods + 1)])
    
    forecast_df = pd.DataFrame({'ds': future_dates, 'yhat': predictions.flatten()})
    return model, forecast_df, scaler # 返回scaler用于后续评估

# --- 4. 机器学习模型 (LightGBM, LR, RF, SVR) --- 
# 将时间序列转换为监督学习问题
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
    n_vars = 1 if type(data) is list else data.shape[1]
    df = pd.DataFrame(data)
    cols, names = list(), list()
    # input sequence (t-n, ... t-1)
    for i in range(n_in, 0, -1):
        cols.append(df.shift(i))
        names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
    # forecast sequence (t, t+1, ... t+n)
    for i in range(0, n_out):
        cols.append(df.shift(-i))
        if i == 0:
            names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
        else:
            names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
    # put it all together
    agg = pd.concat(cols, axis=1)
    agg.columns = names
    # drop rows with NaN values
    if dropnan:
        agg.dropna(inplace=True)
    return agg

def train_predict_ml_model(df, model_type='lgbm', look_back=7, periods=3):
    """训练和预测通用机器学习模型"""
    data_values = df['y'].values.reshape(-1, 1)
    
    # 转换为监督学习问题
    supervised_data = series_to_supervised(data_values, n_in=look_back, n_out=1)
    if supervised_data.empty:
        print(f"错误：为 {model_type} 创建的监督学习数据集为空。")
        return None, None
        
    X = supervised_data.iloc[:, :-1].values
    y = supervised_data.iloc[:, -1].values

    if X.shape[0] < 2 : # 需要至少两条数据来训练
        print(f"错误：为 {model_type} 准备的数据不足 ({X.shape[0]}条)。")
        return None, None

    # 实例化模型
    if model_type == 'lgbm':
        model = lgb.LGBMRegressor(random_state=42)
    elif model_type == 'lr':
        model = LinearRegression()
    elif model_type == 'rf':
        model = RandomForestRegressor(random_state=42)
    elif model_type == 'svr':
        model = SVR()
    else:
        raise ValueError("不支持的模型类型")

    model.fit(X, y)

    # 进行预测
    last_sequence = data_values[-look_back:].flatten() # (look_back,)
    predictions = []
    current_input = last_sequence.copy()

    for _ in range(periods):
        pred = model.predict(current_input.reshape(1, -1))[0]
        predictions.append(pred)
        # 更新输入序列
        current_input = np.roll(current_input, -1)
        current_input[-1] = pred
        
    # 创建预测的日期
    last_date = df['ds'].iloc[-1]
    future_dates = pd.to_datetime([last_date + pd.Timedelta(days=i) for i in range(1, periods + 1)])
    
    forecast_df = pd.DataFrame({'ds': future_dates, 'yhat': np.array(predictions).flatten()})
    return model, forecast_df

# --- 5. 误差评估 --- 
def calculate_errors(true_values, predicted_values):
    mae = mean_absolute_error(true_values, predicted_values)
    rmse = np.sqrt(mean_squared_error(true_values, predicted_values))
    mape = np.mean(np.abs((true_values - predicted_values) / true_values)) * 100
    return mae, rmse, mape

def evaluate_model_on_history(df, model_name, model, scaler=None, look_back=7, is_prophet=False, is_lstm=False, is_ml=False):
    """在历史数据上进行回测评估模型"""
    if len(df) < look_back + 10: # 确保有足够数据进行有意义的回测
        print(f"{model_name} 数据太少，无法进行历史回测。")
        return None, None, None

    # 划分训练集和测试集进行回测
    # 通常取最后一部分作为测试集，例如最后20% 或固定的天数
    test_size_ratio = 0.2 
    split_point = int(len(df) * (1 - test_size_ratio))
    train_df = df.iloc[:split_point]
    test_df = df.iloc[split_point:]

    if len(test_df) < 3: # 确保测试集至少有几天数据
        print(f"{model_name} 测试集数据太少 ({len(test_df)}天)，无法进行有效回测。")
        return None, None, None

    true_values = test_df['y'].values
    predictions_eval = []

    if is_prophet:
        m_eval = Prophet()
        m_eval.fit(train_df) # 用训练集训练
        future_eval = m_eval.make_future_dataframe(periods=len(test_df))
        forecast_eval = m_eval.predict(future_eval)
        
        # 确保日期列类型一致
        test_df_copy = test_df.copy() # 创建副本以避免SettingWithCopyWarning
        test_df_copy['ds'] = pd.to_datetime(test_df_copy['ds'])
        forecast_eval['ds'] = pd.to_datetime(forecast_eval['ds'])
        
        # 合并预测以确保日期对齐
        merged_df = pd.merge(test_df_copy[['ds', 'y']], forecast_eval[['ds', 'yhat']], on='ds', how='left')
        
        # 获取真实值和预测值
        true_values_prophet = merged_df['y'].values
        predictions_eval = merged_df['yhat'].values

        # 处理NaN值：只保留真实值和预测值都非NaN的行
        mask = ~np.isnan(true_values_prophet) & ~np.isnan(predictions_eval)
        true_values = true_values_prophet[mask]
        predictions_eval = predictions_eval[mask]

        if len(true_values) == 0 or len(predictions_eval) == 0:
            print(f"警告：{model_name} 回测时，在移除NaN后没有有效数据点。跳过误差计算。")
            return None, None, None

    elif is_lstm:
        data_eval = train_df['y'].values.reshape(-1, 1)
        scaler_eval = MinMaxScaler(feature_range=(0, 1))
        scaled_data_eval = scaler_eval.fit_transform(data_eval)
        
        train_X_eval, train_Y_eval = create_dataset(scaled_data_eval, look_back)
        if train_X_eval.shape[0] == 0: return None,None,None
        train_X_eval = np.reshape(train_X_eval, (train_X_eval.shape[0], 1, train_X_eval.shape[1]))

        m_eval = Sequential()
        m_eval.add(LSTM(50, input_shape=(1, look_back)))
        m_eval.add(Dense(1))
        m_eval.compile(loss='mean_squared_error', optimizer='adam')
        m_eval.fit(train_X_eval, train_Y_eval, epochs=50, batch_size=1, verbose=0)
        
        # 准备LSTM回测的输入数据
        # 我们需要从 train_df 的末尾和 test_df 的开头获取数据来构建初始序列
        combined_y_for_lstm_eval = df['y'].values.reshape(-1,1)
        scaled_combined_y_for_lstm_eval = scaler_eval.transform(combined_y_for_lstm_eval) # 使用训练集的scaler

        inputs = scaled_combined_y_for_lstm_eval[len(scaled_combined_y_for_lstm_eval) - len(test_df) - look_back : len(scaled_combined_y_for_lstm_eval) - len(test_df)]
        inputs = inputs.reshape(-1,1)
        
        temp_predictions_scaled = []
        current_eval_input = inputs[-look_back:].reshape((1, 1, look_back))
        for _ in range(len(test_df)):
            pred_scaled = m_eval.predict(current_eval_input, verbose=0)
            temp_predictions_scaled.append(pred_scaled[0,0])
            new_val_scaled = pred_scaled.reshape(1,1,1)
            current_eval_input = np.concatenate((current_eval_input[:,:,1:], new_val_scaled), axis=2)
        predictions_eval = scaler_eval.inverse_transform(np.array(temp_predictions_scaled).reshape(-1,1)).flatten()
        
    elif is_ml:
        data_values_eval = train_df['y'].values.reshape(-1, 1)
        supervised_data_eval = series_to_supervised(data_values_eval, n_in=look_back, n_out=1)
        if supervised_data_eval.empty: return None,None,None
        X_train_eval = supervised_data_eval.iloc[:, :-1].values
        y_train_eval = supervised_data_eval.iloc[:, -1].values
        if X_train_eval.shape[0] < 2: return None,None,None

        model_eval_instance = type(model)() # 创建同类型的新实例
        if hasattr(model, 'random_state'): # 保持随机状态一致性（如果模型有此参数）
            model_eval_instance.random_state = model.random_state
        model_eval_instance.fit(X_train_eval, y_train_eval)
        
        # 准备ML回测的输入数据
        # 我们需要从 train_df 的末尾和 test_df 的开头获取数据来构建初始序列
        historical_data_for_ml_eval = df['y'].values
        
        temp_predictions = []
        for i in range(len(test_df)):
            # 构建当前预测的输入特征
            # 需要 train_df 的最后 look_back-1 个点 和 test_df 的第 i 个点之前的真实值（或预测值）
            # 为了简化，这里我们假设在回测时，每一步都用真实历史数据（如果可用）或前一步的预测来构建特征
            # 一个更严谨的回测会滚动预测
            if i == 0:
                current_input_features = historical_data_for_ml_eval[split_point - look_back : split_point].flatten()
            else:
                # 更新序列，用上一个预测值
                current_input_features = np.roll(current_input_features, -1)
                current_input_features[-1] = temp_predictions[-1] # 使用上一个预测值
            
            pred = model_eval_instance.predict(current_input_features.reshape(1, -1))[0]
            temp_predictions.append(pred)
        predictions_eval = np.array(temp_predictions)

    else:
        print(f"未知的模型类型 {model_name} 用于回测。")
        return None, None, None

    if len(predictions_eval) != len(true_values):
        print(f"警告：{model_name} 回测时预测值数量 ({len(predictions_eval)}) 与真实值数量 ({len(true_values)}) 不匹配。跳过误差计算。")
        # 尝试截断或填充，但更可能是逻辑问题
        min_len = min(len(predictions_eval), len(true_values))
        if min_len > 0:
            predictions_eval = predictions_eval[:min_len]
            true_values = true_values[:min_len]
        else:
            return None, None, None
            
    mae, rmse, mape = calculate_errors(true_values, predictions_eval)
    print(f"--- {model_name} 回测误差指标 ---")
    print(f"MAE: {mae:.4f}")
    print(f"RMSE: {rmse:.4f}")
    print(f"MAPE: {mape:.2f}%")
    return mae, rmse, mape
# 确保在调用 calculate_errors 之前，true_values 和 predictions_eval 中没有 NaN
    # （Prophet 的情况已经在上面处理了，其他模型如果需要也应添加类似逻辑）
    if np.isnan(predictions_eval).any() or np.isnan(true_values).any():
        print(f"警告：{model_name} 的预测值或真实值中仍存在NaN，无法计算误差。")
        # 进一步处理或直接返回
        valid_mask = ~np.isnan(true_values) & ~np.isnan(predictions_eval)
        true_values = true_values[valid_mask]
        predictions_eval = predictions_eval[valid_mask]
        if len(true_values) == 0:
            return None, None, None

    mae, rmse, mape = calculate_errors(true_values, predictions_eval)
    
# --- 6. 可视化 --- 
def plot_forecast(df_actual, forecast_df, model_name, conf_int=False, title_suffix=''):
    """绘制历史数据和预测结果"""
    plt.figure(figsize=(15, 7))
    
    # 绘制历史数据 (最近N天 + 预测期)
    # 为了使预测更清晰，可以只显示部分历史数据
    display_history_days = 30 
    if len(df_actual) > display_history_days:
        plot_actual_df = df_actual.iloc[-display_history_days:]
    else:
        plot_actual_df = df_actual

    plt.plot(plot_actual_df['ds'], plot_actual_df['y'], label='历史真实值', color='blue')
    
    # 绘制预测值
    plt.plot(forecast_df['ds'], forecast_df['yhat'], label=f'{model_name} 预测值', color='red', linestyle='--')

    # 绘制置信区间 (如果Prophet模型提供)
    if conf_int and 'yhat_lower' in forecast_df.columns and 'yhat_upper' in forecast_df.columns:
        plt.fill_between(forecast_df['ds'], 
                         forecast_df['yhat_lower'], 
                         forecast_df['yhat_upper'], 
                         color='pink', alpha=0.3, label='置信区间')

    plt.title(f'{model_name} 汇率预测 - {title_suffix}', fontproperties=font)
    plt.xlabel('日期', fontproperties=font)
    plt.ylabel('汇率', fontproperties=font)
    plt.legend(prop=font)
    plt.grid(True)
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
    plt.xticks(rotation=45)
    
    # 添加历史数据和预测数据的分界线
    if not plot_actual_df.empty and not forecast_df.empty:
        last_hist_date = plot_actual_df['ds'].iloc[-1]
        first_pred_date = forecast_df['ds'].iloc[0]
        # 通常分界线在最后一个历史点之后，第一个预测点之前
        # 如果历史数据和预测数据是连续的，分界线可以设在它们之间
        if last_hist_date < first_pred_date:
             plt.axvline(last_hist_date + pd.Timedelta(days=0.5), color='gray', linestyle=':', linewidth=2, label='历史/预测分界')

    # 自动调整y轴范围，使预测更清晰
    if not forecast_df.empty:
        min_val = min(plot_actual_df['y'].min() if not plot_actual_df.empty else float('inf'), forecast_df['yhat'].min())
        max_val = max(plot_actual_df['y'].max() if not plot_actual_df.empty else float('-inf'), forecast_df['yhat'].max())
        if 'yhat_upper' in forecast_df.columns:
            max_val = max(max_val, forecast_df['yhat_upper'].max())
        padding = (max_val - min_val) * 0.1 # 10% padding
        plt.ylim(min_val - padding, max_val + padding)

    plt.tight_layout()
    plot_filename = f"{model_name.lower().replace(' ', '_')}_prediction_analysis_{title_suffix.replace(' ', '_')}.png"
    plt.savefig(plot_filename)
    print(f"图表已保存为: {plot_filename}")
    plt.show()

def plot_multi_model_comparison(df_actual, all_forecasts, prediction_periods, title_suffix='未来3天预测'):
    """绘制多个模型预测结果的对比图"""
    plt.figure(figsize=(16, 8))

    # 显示最近15天的历史数据，避免横坐标过于密集
    display_history_days = 15
    if len(df_actual) > display_history_days:
        plot_actual_df = df_actual.iloc[-display_history_days:].copy()
    else:
        plot_actual_df = df_actual.copy()
    
    if plot_actual_df.empty:
        print("警告：没有历史数据可显示")
        return
    
    # 绘制历史真实值
    plt.plot(plot_actual_df['ds'], plot_actual_df['y'], 
             label='历史真实值', color='black', linewidth=2.5, alpha=0.8)

    # 为不同模型定义更丰富的颜色和线型组合
    model_styles = {
        'Prophet': {'color': '#FF6B6B', 'linestyle': '-', 'marker': 'o'},
        'LSTM': {'color': '#4ECDC4', 'linestyle': '-', 'marker': 's'},
        'LightGBM': {'color': '#45B7D1', 'linestyle': '-', 'marker': '^'},
        'LinearRegression': {'color': '#96CEB4', 'linestyle': '-', 'marker': 'D'},
        'RandomForest': {'color': '#FFEAA7', 'linestyle': '-', 'marker': 'v'},
        'SVR': {'color': '#DDA0DD', 'linestyle': '-', 'marker': 'p'}
    }
    
    # 备用颜色（如果模型名称不在预定义中）
    backup_colors = ['#FF9F43', '#10AC84', '#EE5A24', '#0984E3', '#A29BFE', '#FD79A8']
    backup_markers = ['*', 'h', '+', 'x', '|', '_']
    
    color_idx = 0
    for model_name, forecast_df in all_forecasts.items():
        if forecast_df is not None and not forecast_df.empty:
            # 确保只绘制指定预测期的数据
            plot_forecast_df = forecast_df.copy()
            # Prophet的预测可能包含历史拟合，我们只取最后prediction_periods个点
            if 'Prophet' in model_name: 
                plot_forecast_df = forecast_df.iloc[-prediction_periods:]
            
            # 获取样式配置
            if model_name in model_styles:
                style = model_styles[model_name]
            else:
                style = {
                    'color': backup_colors[color_idx % len(backup_colors)],
                    'linestyle': '-',
                    'marker': backup_markers[color_idx % len(backup_markers)]
                }
            
            plt.plot(plot_forecast_df['ds'], plot_forecast_df['yhat'], 
                     label=f'{model_name} 预测', 
                     color=style['color'],
                     linestyle=style['linestyle'],
                     marker=style['marker'],
                     markersize=6,
                     linewidth=2.5,
                     alpha=0.9)  # 增加透明度使线条更清晰
            color_idx += 1

    # 添加历史数据和预测数据的分界线
    if not plot_actual_df.empty and all_forecasts:
        last_hist_date = plot_actual_df['ds'].iloc[-1]
        plt.axvline(last_hist_date, color='gray', linestyle=':', 
                   linewidth=2, alpha=0.7, label='历史/预测分界')

    plt.title(f'多模型汇率预测对比 - {title_suffix}', fontproperties=font, fontsize=16)
    plt.xlabel('日期', fontproperties=font, fontsize=12)
    plt.ylabel('汇率', fontproperties=font, fontsize=12)
    plt.legend(prop=font, loc='best', frameon=True, fancybox=True, shadow=True)
    plt.grid(True, alpha=0.3)
    
    # 优化x轴刻度显示，参考图片样式
    ax = plt.gca()
    # 使用月刻度，显示年-月格式
    ax.xaxis.set_major_locator(mdates.MonthLocator())
    ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
    plt.xticks(rotation=45)

    # 自动调整y轴范围
    min_val_hist = plot_actual_df['y'].min() if not plot_actual_df.empty else float('inf')
    max_val_hist = plot_actual_df['y'].max() if not plot_actual_df.empty else float('-inf')
    min_val_pred = min((f['yhat'].min() for f in all_forecasts.values() if f is not None and not f.empty), default=float('inf'))
    max_val_pred = max((f['yhat'].max() for f in all_forecasts.values() if f is not None and not f.empty), default=float('-inf'))
    
    min_val = min(min_val_hist, min_val_pred)
    max_val = max(max_val_hist, max_val_pred)

    if min_val != float('inf') and max_val != float('-inf'):
        padding = (max_val - min_val) * 0.1 # 10% padding
        plt.ylim(min_val - padding, max_val + padding)

    plt.tight_layout()
    plot_filename = f"multi_model_comparison_{title_suffix.replace(' ', '_')}.png"
    plt.savefig(plot_filename, dpi=300, bbox_inches='tight')  # 提高保存质量
    print(f"多模型对比图已保存为: {plot_filename}")
    plt.show()

if __name__ == '__main__':
    # --- 配置参数 ---
    file_path = 'D:/13langgraph/旧的/usd_cny.csv'  # 修改为您的数据文件名
    prediction_periods = 3 # 要预测的未来天数
    lstm_look_back = 7
    ml_look_back = 7

    # --- 加载数据 ---
    df_original = load_data(file_path)  # 移除了不需要的参数

    if df_original is None:
        exit()

    all_models_forecasts = {}
    all_models_errors = {}

    # --- 1. Prophet --- 
    print("\n--- 正在运行 Prophet 模型 ---")
    prophet_model, prophet_forecast = train_predict_prophet(df_original.copy(), periods=prediction_periods)
    if prophet_forecast is not None:
        all_models_forecasts['Prophet'] = prophet_forecast.iloc[-prediction_periods:]
        mae, rmse, mape = evaluate_model_on_history(df_original.copy(), 'Prophet', prophet_model, is_prophet=True)
        if mae is not None: all_models_errors['Prophet'] = {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}

    # --- 2. LSTM --- 
    print("\n--- 正在运行 LSTM 模型 ---")
    # 注意：LSTM训练可能较慢
    lstm_model, lstm_forecast_df, lstm_scaler = train_predict_lstm(df_original.copy(), look_back=lstm_look_back, periods=prediction_periods, epochs=50)
    if lstm_forecast_df is not None:
        all_models_forecasts['LSTM'] = lstm_forecast_df
        mae, rmse, mape = evaluate_model_on_history(df_original.copy(), 'LSTM', lstm_model, scaler=lstm_scaler, look_back=lstm_look_back, is_lstm=True)
        if mae is not None: all_models_errors['LSTM'] = {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}

    # --- 3. LightGBM --- 
    print("\n--- 正在运行 LightGBM 模型 ---")
    lgbm_model, lgbm_forecast_df = train_predict_ml_model(df_original.copy(), model_type='lgbm', look_back=ml_look_back, periods=prediction_periods)
    if lgbm_forecast_df is not None:
        all_models_forecasts['LightGBM'] = lgbm_forecast_df
        mae, rmse, mape = evaluate_model_on_history(df_original.copy(), 'LightGBM', lgbm_model, look_back=ml_look_back, is_ml=True)
        if mae is not None: all_models_errors['LightGBM'] = {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}

    # --- 4. Linear Regression --- 
    print("\n--- 正在运行 线性回归 模型 ---")
    lr_model, lr_forecast_df = train_predict_ml_model(df_original.copy(), model_type='lr', look_back=ml_look_back, periods=prediction_periods)
    if lr_forecast_df is not None:
        all_models_forecasts['LinearRegression'] = lr_forecast_df
        mae, rmse, mape = evaluate_model_on_history(df_original.copy(), 'LinearRegression', lr_model, look_back=ml_look_back, is_ml=True)
        if mae is not None: all_models_errors['LinearRegression'] = {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}

    # --- 5. Random Forest --- 
    print("\n--- 正在运行 随机森林 模型 ---")
    rf_model, rf_forecast_df = train_predict_ml_model(df_original.copy(), model_type='rf', look_back=ml_look_back, periods=prediction_periods)
    if rf_forecast_df is not None:
        all_models_forecasts['RandomForest'] = rf_forecast_df
        mae, rmse, mape = evaluate_model_on_history(df_original.copy(), 'RandomForest', rf_model, look_back=ml_look_back, is_ml=True)
        if mae is not None: all_models_errors['RandomForest'] = {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}
    if rf_forecast_df is not None:
        all_models_forecasts['RandomForest'] = rf_forecast_df
        mae, rmse, mape = evaluate_model_on_history(df_original.copy(), 'RandomForest', rf_model, look_back=ml_look_back, is_ml=True)
        if mae is not None: all_models_errors['RandomForest'] = {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}
    # --- 6. SVR --- 
    print("\n--- 正在运行 SVR 模型 ---")
    svr_model, svr_forecast_df = train_predict_ml_model(df_original.copy(), model_type='svr', look_back=ml_look_back, periods=prediction_periods)
    if svr_forecast_df is not None:
        all_models_forecasts['SVR'] = svr_forecast_df
        mae, rmse, mape = evaluate_model_on_history(df_original.copy(), 'SVR', svr_model, look_back=ml_look_back, is_ml=True)
        if mae is not None: all_models_errors['SVR'] = {'MAE': mae, 'RMSE': rmse, 'MAPE': mape}

    # --- 打印所有模型的误差指标 --- 
    print("\n--- 所有模型回测误差总结 ---")
    for model_name, errors in all_models_errors.items():
        print(f"  {model_name}: MAE={errors['MAE']:.4f}, RMSE={errors['RMSE']:.4f}, MAPE={errors['MAPE']:.2f}%")
    if not all_models_errors:
        print("没有模型成功计算误差指标。")
    else:
        for model_name, errors in all_models_errors.items():
            print(f"模型: {model_name}")
            print(f"  MAE: {errors['MAE']:.4f}")
            print(f"  RMSE: {errors['RMSE']:.4f}")
            print(f"  MAPE: {errors['MAPE']:.2f}%")
            print("---")
    # --- 绘制多模型对比图 ---
    if all_models_forecasts:
        print("\n--- 正在生成多模型预测对比图 ---")
        plot_multi_model_comparison(df_original, all_models_forecasts, prediction_periods=prediction_periods, title_suffix=f'未来{prediction_periods}天预测')
    else:
        print("没有可用于绘制对比图的预测结果。")

    # --- 用户选择模型进行绘图 --- 
    if not all_models_forecasts:
        print("\n没有模型成功生成预测，无法绘图。")
    else:
        print("\n可用于绘图的模型:")
        available_models_for_plot = list(all_models_forecasts.keys())
        for i, model_name in enumerate(available_models_for_plot):
            print(f"{i+1}. {model_name}")
        
        while True:
            try:
                choice = input(f"请选择要绘制预测图的模型编号 (1-{len(available_models_for_plot)}), 或输入 'q' 退出: ")
                if choice.lower() == 'q':
                    break
                choice_idx = int(choice) - 1
                if 0 <= choice_idx < len(available_models_for_plot):
                    selected_model_name = available_models_for_plot[choice_idx]
                    selected_forecast = all_models_forecasts[selected_model_name]
                    
                    # Prophet的预测包含更多列，需要特殊处理
                    is_prophet_plot = selected_model_name == 'Prophet'
                    plot_forecast(df_original, 
                                  selected_forecast, 
                                  selected_model_name, 
                                  conf_int=is_prophet_plot, 
                                  title_suffix='未来3天预测')
                    break 
                else:
                    print("无效的选择，请输入列表中的编号。")
            except ValueError:
                print("无效的输入，请输入数字编号。")

    print("\n脚本执行完毕。")
    print("\n--- 单独模型预测结果可视化 ---")